"""ETF批量分析工作流脚本.

该脚本实现批量处理多个ETF的完整量化分析流程：
1. 数据获取
2. 特征工程
3. 模型训练和回测
4. 模型预测
5. 生成投资分析整合报告
"""

from __future__ import annotations

import json
import logging
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Optional

from src.utils import now_shanghai
from src.utils import ETFDataTool, ETFDataToolInput, ETFDataToolOutput
from src.utils import ETFRealtimeTool, ETFRealtimeToolInput
from src.utils import FeatureEngineeringTool, FeatureEngineeringInput
from src.quant import QuantAnalyzer
from .model_prediction import ModelPredictionTool, ModelPredictionInput

logger = logging.getLogger(__name__)


@dataclass
class PositionInfo:
    """持仓信息配置."""

    quantity: int = 0  # 持仓数量
    available: int = 0  # 可用数量
    cost_price: float = 0.0  # 持仓成本


@dataclass
class ETFConfig:
    """ETF配置信息."""

    code: str  # ETF代码
    name: str  # ETF名称
    position: Optional[PositionInfo] = None  # 持仓信息（可选）


@dataclass
class AnalysisResult:
    """单个ETF分析结果."""

    etf_code: str
    etf_name: str
    success: bool
    etf_config: Optional[ETFConfig] = None  # ETF配置信息
    error_message: Optional[str] = None
    data_result: Optional[Dict[str, Any]] = None
    risk_result: Optional[Dict[str, Any]] = None
    quant_result: Optional[Dict[str, Any]] = None
    prediction_result: Optional[Dict[str, Any]] = None
    realtime_result: Optional[Dict[str, Any]] = None


def analyze_single_etf(etf_config: ETFConfig) -> AnalysisResult:
    """分析单个ETF的完整流程.
    
    按照以下顺序执行：
    1. ETFDataTool: 拉取历史日K数据
    2. FeatureEngineeringTool: 执行特征工程
    3. QuantAnalysisTool: 训练模型、回测、选择最佳模型
    4. ETFRealtimeTool: 获取实时价格
    5. ModelPredictionTool: 使用最佳模型进行预测
    
    Parameters
    ----------
    etf_config:
        ETF配置信息
        
    Returns
    -------
    AnalysisResult
        分析结果
    """
    etf_code = etf_config.code
    etf_name = etf_config.name
    
    logger.info(f"开始分析 ETF: {etf_code} ({etf_name})")
    
    result = AnalysisResult(
        etf_code=etf_code,
        etf_name=etf_name,
        success=False,
        etf_config=etf_config,
    )
    
    try:
        # 1. 获取ETF历史数据
        logger.info(f"[{etf_code}] 步骤1: 获取历史数据...")
        data_tool = ETFDataTool()
        # 使用 _get_json() 方法获取 JSON 格式的结果，而不是 run() 返回的格式化文本
        data_json = data_tool._get_json(symbol=etf_code)
        result.data_result = json.loads(data_json)
        normalized_symbol = result.data_result["normalized_symbol"]
        # 记录数据获取的详细信息：起止日期、实际数据条数、数据截止日期
        data_start = result.data_result.get("start_date", "未知")
        data_end = result.data_result.get("end_date", "未知")
        data_count = result.data_result.get("actual_count", 0)
        logger.info(
            f"[{etf_code}] ✓ 历史数据获取完成: {data_count} 条，"
            f"数据范围: {data_start[:10]} 至 {data_end[:10]}"
        )
        
        # 2. 执行特征工程（启用特征选择）
        logger.info(f"[{etf_code}] 步骤2: 执行特征工程和特征选择...")
        feature_tool = FeatureEngineeringTool()
        # 使用 _get_json() 方法获取 JSON 格式的结果，启用特征选择
        feature_json = feature_tool._get_json(
            symbol=normalized_symbol,
            include_target=True,
            enable_feature_selection=True
        )
        feature_result = json.loads(feature_json)
        
        # 显示特征选择结果
        if feature_result.get("feature_selection", {}).get("enabled"):
            fs_info = feature_result["feature_selection"]
            logger.info(
                f"[{etf_code}] ✓ 特征工程完成: {feature_result['feature_rows']} 行, "
                f"{fs_info['selected_count']} 个特征（原始 {fs_info['original_count']} 个，"
                f"剔除 {fs_info['removed_count']} 个低质量特征）"
            )
        else:
            logger.info(f"[{etf_code}] ✓ 特征工程完成: {feature_result['feature_rows']} 行, {len(feature_result['feature_columns'])} 列")
        
        # 3. 训练模型、回测、选择最佳模型
        logger.info(f"[{etf_code}] 步骤3: 训练模型和回测（可能需要较长时间）...")
        quant_analyzer = QuantAnalyzer()
        # 使用 analyze() 方法获取 JSON 格式的结果
        quant_json = quant_analyzer.analyze(
            symbol=normalized_symbol,
            risk_summary=None,
            include_lstm=True,
            enable_optimization=False,
        )
        result.quant_result = json.loads(quant_json)
        logger.info(f"[{etf_code}] ✓ 模型训练和回测完成，最佳模型: {result.quant_result.get('best_model', 'N/A')}")
        
        # 4. 获取实时价格
        logger.info(f"[{etf_code}] 步骤4: 获取实时价格...")
        realtime_tool = ETFRealtimeTool()
        # 使用 _get_json() 方法获取 JSON 格式的结果
        realtime_json = realtime_tool._get_json(symbol=normalized_symbol)
        result.realtime_result = json.loads(realtime_json)
        logger.info(f"[{etf_code}] ✓ 实时价格获取完成: {result.realtime_result.get('price', 'N/A')}")
        
        # 5. 使用最佳模型进行预测
        logger.info(f"[{etf_code}] 步骤5: 使用最佳模型进行预测...")
        prediction_tool = ModelPredictionTool()
        # 使用 _get_json() 方法获取 JSON 格式的结果
        prediction_json = prediction_tool._get_json(symbol=normalized_symbol, model_name=None)
        result.prediction_result = json.loads(prediction_json)
        logger.info(f"[{etf_code}] ✓ 预测完成: {result.prediction_result.get('prediction', 'N/A')}")
        
        result.success = True
        logger.info(f"[{etf_code}] ✓ 所有步骤完成")
        
    except Exception as e:
        logger.error(f"[{etf_code}] ✗ 分析失败: {e}", exc_info=True)
        result.error_message = str(e)
        result.success = False
    
    return result


def generate_report(
    result: AnalysisResult,
    current_time: str,
    model_name: str = "Composer",
    output_dir: str = "out",
    position_info: Optional[PositionInfo] = None,
) -> str:
    """生成投资分析整合报告.
    
    按照用户提供的模板格式生成Markdown报告。
    
    Parameters
    ----------
    result:
        分析结果
    current_time:
        当前时间（ISO格式）
    model_name:
        大模型名称
    output_dir:
        输出目录
        
    Returns
    -------
    str
        报告文件路径
    """
    if not result.success:
        # 如果分析失败，生成简短的错误报告
        report_content = f"""# 投资分析整合报告 - ETF基金代码：{result.etf_code}

**报告生成时间**：{current_time}
**当前使用的大模型名称**：{model_name}

## 分析失败

ETF代码：{result.etf_code}
ETF名称：{result.etf_name}

**错误信息**：{result.error_message or "未知错误"}

请检查数据源和网络连接，然后重试。
"""
    else:
        # 解析各个结果
        data_result = result.data_result or {}
        quant_result = result.quant_result or {}
        prediction_result = result.prediction_result or {}
        realtime_result = result.realtime_result or {}
        
        # 提取关键信息
        normalized_symbol = data_result.get("normalized_symbol", result.etf_code)
        etf_name = result.etf_name
        
        # 实时价格信息
        current_price = realtime_result.get("price", 0.0)
        change_percent = realtime_result.get("change_percent")
        if change_percent is None:
            change_percent = 0.0
        as_of_time = realtime_result.get("as_of", current_time)
        
        # 持仓信息（从配置中获取）
        if position_info:
            position_info_str = f"持仓数量：{position_info.quantity}，可用数量：{position_info.available}，持仓成本：{position_info.cost_price:.3f}元"
        else:
            position_info_str = "持仓数量：0，可用数量：0，持仓成本：0.000元"
        
        # 预测结果
        up_probability = prediction_result.get("up_probability", 0.5)
        prediction = prediction_result.get("prediction", "未知")
        confidence = prediction_result.get("confidence", 0.0)
        prediction_date = prediction_result.get("prediction_date", current_time)
        
        # 风险指标（从量化分析结果中获取）
        # 注意：风险分析工具已移除，风险指标从量化分析结果中获取
        max_drawdown = 0.0
        volatility_20d = 0.0
        avg_volume = 0.0
        # 尝试从量化分析结果中获取风险指标
        if quant_result:
            backtest_metrics = quant_result.get("backtest_metrics", {})
            best_model = quant_result.get("best_model", "")
            if best_model and best_model in backtest_metrics:
                best_metrics = backtest_metrics[best_model]
                max_drawdown = abs(best_metrics.get("max_drawdown", 0.0))
                volatility_20d = best_metrics.get("annualized_volatility", 0.0)
        
        # 量化分析结果
        best_model = quant_result.get("best_model", "未知")
        backtest_metrics = quant_result.get("backtest_metrics", {})
        best_model_metrics = backtest_metrics.get(best_model, {})
        best_downside_vol = best_model_metrics.get("downside_volatility", 0.0) * 100
        best_sortino = best_model_metrics.get("sortino_ratio", 0.0)
        best_calmar = best_model_metrics.get("calmar_ratio", 0.0)
        best_tracking_error = best_model_metrics.get("tracking_error", 0.0) * 100
        best_excess_return = best_model_metrics.get("excess_return", 0.0) * 100
        
        # 原始数据信息（从 data_result 获取）
        data_start_date = data_result.get("start_date", "未知")
        data_end_date = data_result.get("end_date", "未知")
        data_actual_count = data_result.get("actual_count", 0)
        # 格式化日期显示（只取日期部分，去掉时间）
        data_start_display = data_start_date[:10] if isinstance(data_start_date, str) and len(data_start_date) >= 10 else data_start_date
        data_end_display = data_end_date[:10] if isinstance(data_end_date, str) and len(data_end_date) >= 10 else data_end_date
        
        # 回测数据信息（从 quant_result 获取）
        data_info = quant_result.get("data_info", {})
        backtest_start_date = data_info.get("start_date", "未知")
        backtest_end_date = data_info.get("end_date", "未知")
        backtest_total_samples = data_info.get("total_samples", 0)
        train_samples = data_info.get("train_samples", 0)
        val_samples = data_info.get("val_samples", 0)
        test_samples = data_info.get("test_samples", 0)
        # 格式化日期显示（只取日期部分，去掉时间）
        backtest_start_display = backtest_start_date[:10] if isinstance(backtest_start_date, str) and len(backtest_start_date) >= 10 else backtest_start_date
        backtest_end_display = backtest_end_date[:10] if isinstance(backtest_end_date, str) and len(backtest_end_date) >= 10 else backtest_end_date
        
        # 生成模型回测对比表格
        # 表格包含：模型名称、年化收益、累计收益、最大回撤、夏普、Sortino、Calmar、胜率、盈亏比、总交易次数、过拟合状态
        model_table_rows = []
        # 按模型名称排序，确保buy_and_hold在最后
        sorted_models = sorted(backtest_metrics.keys(), key=lambda x: (x == "buy_and_hold", x))
        
        for model_name in sorted_models:
            metrics = backtest_metrics.get(model_name, {})
            # 标记最佳模型
            model_display_name = model_name
            if model_name == best_model:
                model_display_name = f"**{model_name}** ⭐"
            elif model_name == "buy_and_hold":
                model_display_name = f"{model_name} (基准)"
            
            # 检查是否过拟合
            is_overfitting = metrics.get("is_overfitting", False)
            overfitting_mark = "⚠️ 过拟合" if is_overfitting else ""
            
            annual_return = metrics.get("annualized_return", 0.0) * 100
            cumulative_return = metrics.get("cumulative_return", metrics.get("total_return", 0.0)) * 100
            max_dd = abs(metrics.get("max_drawdown", 0.0)) * 100
            sharpe = metrics.get("sharpe_ratio", 0.0)
            sortino = metrics.get("sortino_ratio", 0.0)
            calmar = metrics.get("calmar_ratio", 0.0)
            win_rate = metrics.get("win_rate", 0.0) * 100
            total_trades = metrics.get("total_trades", 0)
            gain_loss_ratio = metrics.get("gain_loss_ratio", 0.0)
            
            model_table_rows.append(
                f"| {model_display_name} | {annual_return:+.2f}% | {cumulative_return:+.2f}% | {max_dd:.2f}% | {sharpe:.2f} | {sortino:.2f} | {calmar:.2f} | {win_rate:.1f}% | {gain_loss_ratio:.2f} | {total_trades} | {overfitting_mark} |"
            )
        
        # 如果没有任何模型数据，添加一行提示
        if not model_table_rows:
            model_table_rows.append("| 暂无数据 | - | - | - | - | - | - |")
        
        model_comparison_table = "\n".join([
            "| 模型 | 年化收益 | 累计收益 | 最大回撤 | 夏普 | Sortino | Calmar | 胜率 | 盈亏比 | 交易次数 | 过拟合状态 |",
            "|------|---------|---------|---------|------|---------|--------|------|--------|---------|-----------|",
        ] + model_table_rows)
        
        # 计算目标价位（基于预测概率和当前价格，未来5-10天内可能达到的价格）
        # 逻辑说明：
        # - 最可能价格：基于预测的中性预期（未来5-10天内最可能达到的价格）
        # - 乐观价格：乐观情况下未来5-10天内可能达到的价格（如果预测上涨则涨幅更大，如果预测下跌则跌幅较小）
        # - 风险价格：风险情况下未来5-10天内可能达到的价格（如果预测上涨则涨幅较小或下跌，如果预测下跌则跌幅较大）
        # 正常情况下：乐观价格 > 最可能价格 > 风险价格
        if up_probability > 0.5:
            # 上涨预测
            expected_change = (up_probability - 0.5) * 0.1  # 预期涨幅（简化计算）
            target_price_most = current_price * (1 + expected_change * 0.5)  # 中等涨幅（最可能）
            target_price_optimistic = current_price * (1 + expected_change)  # 更大涨幅（乐观）
            target_price_risk = current_price * (1 + expected_change * 0.2)  # 较小涨幅或接近当前价格（风险）
        else:
            # 下跌预测
            expected_change = (0.5 - up_probability) * 0.1  # 预期跌幅
            target_price_most = current_price * (1 - expected_change * 0.5)  # 中等跌幅（最可能）
            target_price_optimistic = current_price * (1 - expected_change * 0.2)  # 较小跌幅（乐观，价格较高）
            target_price_risk = current_price * (1 - expected_change)  # 较大跌幅（风险，价格较低）
        
        # 生成精简报告内容（只保留关键信息）
        report_content = f"""# 投资分析报告 - {normalized_symbol} ({etf_name})

**报告时间**：{as_of_time[:10]} {as_of_time[11:19]}

## 一、当前状态

- **当前价格**：{current_price:.4f}元（涨跌幅：{change_percent*100:+.2f}%）
- **持仓情况**：{position_info_str}

## 二、预测结果

- **涨跌方向**：{prediction}
{"" if abs(up_probability + confidence - 1.0) < 0.001 else f"- **上涨概率**：{up_probability*100:.1f}%\n"}- **预测置信度**：{confidence*100:.1f}%
- **目标价位**（未来5-10天内可能达到的价格）：
  - 1. 最可能达到的价格：{target_price_most:.4f}元
  - 2. 乐观情况下价格：{target_price_optimistic:.4f}元
  - 3. 风险情况下价格：{target_price_risk:.4f}元

## 三、交易建议

- **操作建议**：{'买入' if up_probability > 0.6 else '持有' if up_probability > 0.4 else '卖出'}
- **止损位**：{current_price * 0.95:.4f}元（-5%）
- **止盈位**：
  - 第一目标：{target_price_most:.4f}元（卖出30%-40%）
  - 第二目标：{target_price_optimistic:.4f}元（再卖出30%）
- **仓位管理**：建议占比不超过20%

## 四、模型回测对比

**数据范围说明**：
- **原始数据范围**：{data_start_display} 至 {data_end_display}（{data_actual_count}条，全部历史数据）
- **回测数据范围**：{backtest_start_display} 至 {backtest_end_display}（{backtest_total_samples}个交易日，仅训练集）
- **数据划分**：训练集 {train_samples} 条，验证集 {val_samples} 条，测试集 {test_samples} 条（测试集保留用于未来验证）

{model_comparison_table}

**说明**：
- ⭐ 标记为最佳模型（基于综合评分：核心指标40%、风险30%、交易质量15%、稳健性15%，已排除过拟合模型）
- ⚠️ 过拟合：表示模型在训练集上表现明显优于验证集，泛化能力较差，不建议使用

## 五、风险指标

- **波动率**：{volatility_20d*100:.2f}%（20日）
- **下行波动率（最佳模型）**：{best_downside_vol:.2f}%（年化）
- **Sortino/Calmar（最佳模型）**：Sortino {best_sortino:.2f}，Calmar {best_calmar:.2f}
- **历史最大跌幅**：{abs(max_drawdown)*100:.2f}%
- **跟踪误差 / 超额收益**：{best_tracking_error:.2f}% / {best_excess_return:+.2f}%

"""

        robustness_info = quant_result.get("robustness", {})
        if robustness_info:
            sensitivity_info = robustness_info.get("sensitivity_results", {})
            sensitivity_lines = []
            bucket_labels = {
                "signal_threshold": "信号阈值",
                "risk_threshold": "止损/止盈",
                "window": "回测窗口",
            }
            for bucket_name, entries in sensitivity_info.items():
                if not entries:
                    continue
                bucket_title = bucket_labels.get(bucket_name, bucket_name)
                scenario_summaries = []
                for entry in entries:
                    metrics = entry.get("metrics", {})
                    sharpe_val = metrics.get("sharpe_ratio", 0.0)
                    max_dd_val = abs(metrics.get("max_drawdown", 0.0)) * 100
                    scenario_summaries.append(
                        f"{entry.get('config_name')}: 夏普 {sharpe_val:.2f} / 最大回撤 {max_dd_val:.2f}%"
                    )
                sensitivity_lines.append(
                    f"- **{bucket_title}**：{'；'.join(scenario_summaries)}"
                )

            time_segments = robustness_info.get("time_segments", [])
            time_rows = [
                "| 时段 | 年化收益 | 夏普 | 最大回撤 |",
                "|------|---------|------|-----------|",
            ]
            for seg in time_segments:
                metrics = seg.get("metrics", {})
                time_rows.append(
                    f"| {seg.get('segment')} | {metrics.get('annualized_return', 0.0)*100:+.2f}% | "
                    f"{metrics.get('sharpe_ratio', 0.0):.2f} | {abs(metrics.get('max_drawdown', 0.0))*100:.2f}% |"
                )

            market_states = robustness_info.get("market_regimes", [])
            market_rows = [
                "| 市场状态 | 年化收益 | 夏普 | 胜率 |",
                "|----------|---------|------|------|",
            ]
            for state in market_states:
                metrics = state.get("metrics", {})
                market_rows.append(
                    f"| {state.get('regime')} | {metrics.get('annualized_return', 0.0)*100:+.2f}% | "
                    f"{metrics.get('sharpe_ratio', 0.0):.2f} | {metrics.get('win_rate', 0.0)*100:.1f}% |"
                )

            alerts = robustness_info.get("risk_alerts", [])
            alert_lines = "\n".join(f"- {alert}" for alert in alerts) if alerts else "- 暂无额外风险提示"

            report_content += "\n## 六、稳健性分析\n\n"
            if sensitivity_lines:
                report_content += "### 参数敏感性\n" + "\n".join(sensitivity_lines) + "\n\n"
            if len(time_rows) > 2:
                report_content += "### 时间分段表现\n" + "\n".join(time_rows) + "\n\n"
            if len(market_rows) > 2:
                report_content += "### 市场状态表现\n" + "\n".join(market_rows) + "\n\n"
            report_content += "### 风险提示\n" + alert_lines + "\n\n"

        recommendation = quant_result.get("recommendation")
        if recommendation:
            ordinal = "七" if robustness_info else "六"
            primary = recommendation.get("primary", {})
            backup = recommendation.get("backup")
            primary_metrics = primary.get("metrics", {})
            primary_breakdown = primary.get("score_breakdown", {})
            report_content += f"## {ordinal}、模型推荐\n\n"
            report_content += (
                f"- **推荐模型**：{primary.get('model', 'N/A')}（综合得分 {primary.get('score', 0.0):.2f}，"
                f"核心 {primary_breakdown.get('core', 0.0):.2f} / 风险 {primary_breakdown.get('risk', 0.0):.2f} / "
                f"交易 {primary_breakdown.get('trading', 0.0):.2f} / 稳健性 {primary_breakdown.get('robustness', 0.0):.2f}）\n"
            )
            report_content += (
                "  - 核心指标："
                f"年化收益 {primary_metrics.get('annualized_return', 0.0)*100:+.2f}%、"
                f"夏普 {primary_metrics.get('sharpe_ratio', 0.0):.2f}、"
                f"信息比率 {primary_metrics.get('information_ratio', 0.0):.2f}\n"
            )
            report_content += (
                "  - 风险控制："
                f"最大回撤 {abs(primary_metrics.get('max_drawdown', 0.0))*100:.2f}%、"
                f"波动率 {primary_metrics.get('annualized_volatility', 0.0)*100:.2f}%、"
                f"下行波动率 {primary_metrics.get('downside_volatility', 0.0)*100:.2f}%\n"
            )
            report_content += (
                "  - 交易质量："
                f"胜率 {primary_metrics.get('win_rate', 0.0)*100:.1f}%、"
                f"盈亏比 {primary_metrics.get('gain_loss_ratio', 0.0):.2f}、"
                f"交易次数 {primary_metrics.get('total_trades', 0)} 次\n"
            )
            reasons = primary.get("reasons", [])
            if reasons:
                report_content += "- **使用注意**：\n" + "\n".join(f"  - {reason}" for reason in reasons) + "\n"
            if backup:
                backup_breakdown = backup.get("score_breakdown", {})
                report_content += (
                    f"- **备选模型**：{backup.get('model')}（得分 {backup.get('score', 0.0):.2f}，"
                    f"核心 {backup_breakdown.get('core', 0.0):.2f} / 风险 {backup_breakdown.get('risk', 0.0):.2f}）\n"
                )
            notes = recommendation.get("notes", [])
            if notes:
                report_content += "- **说明**：\n" + "\n".join(f"  - {note}" for note in notes) + "\n"
            report_content += "\n"
    
    # 创建输出目录
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    # 生成时间戳（格式：YYYYMMDD_HHMM）
    timestamp = now_shanghai().strftime("%Y%m%d_%H%M")
    
    # 生成文件名
    filename = f"{result.etf_code}_analysis_report_{timestamp}.md"
    filepath = output_path / filename
    
    # 写入文件
    filepath.write_text(report_content, encoding="utf-8")
    logger.info(f"报告已生成: {filepath}")
    
    return str(filepath)


def batch_analyze_etfs(
    etf_configs: list[ETFConfig],
    max_workers: int = 3,
    output_dir: str = "out",
    model_name: str = "Composer",
) -> Dict[str, AnalysisResult]:
    """批量分析多个ETF.
    
    使用线程池并发处理多个ETF，提高处理效率。
    
    Parameters
    ----------
    etf_configs:
        ETF配置列表
    max_workers:
        最大并发数（默认3）
    output_dir:
        输出目录
    model_name:
        大模型名称
        
    Returns
    -------
    Dict[str, AnalysisResult]
        分析结果字典，key为ETF代码
    """
    current_time = now_shanghai().isoformat()
    results: Dict[str, AnalysisResult] = {}
    
    logger.info(f"开始批量分析 {len(etf_configs)} 个ETF，并发数: {max_workers}")
    
    # 使用线程池并发处理
    # 注意：虽然使用了并发，但由于GLOBAL_DATA_STORE是全局的，可能存在线程安全问题
    # 在实际使用中，如果出现数据混乱，可以考虑使用进程池或加锁
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 提交所有任务，直接保存 config 对象，便于后续使用
        future_to_config = {
            executor.submit(analyze_single_etf, config): config
            for config in etf_configs
        }
        
        # 处理完成的任务
        for future in as_completed(future_to_config):
            config = future_to_config[future]
            etf_code = config.code
            try:
                result = future.result()
                results[etf_code] = result
                
                # 从结果对象中直接获取持仓信息
                position_info = result.etf_config.position if result.etf_config else None
                
                # 生成报告
                if result.success:
                    report_path = generate_report(result, current_time, model_name, output_dir, position_info)
                    logger.info(f"[{etf_code}] 报告已生成: {report_path}")
                else:
                    # 即使失败也生成错误报告
                    report_path = generate_report(result, current_time, model_name, output_dir, position_info)
                    logger.warning(f"[{etf_code}] 分析失败，已生成错误报告: {report_path}")
                    
            except Exception as e:
                logger.error(f"[{etf_code}] 处理异常: {e}", exc_info=True)
                # 从 future 映射中获取配置
                config = future_to_config.get(future)
                # 创建失败结果
                failed_result = AnalysisResult(
                    etf_code=etf_code,
                    etf_name=config.name if config else "未知",
                    success=False,
                    etf_config=config,
                    error_message=str(e),
                )
                results[etf_code] = failed_result
                # 从结果对象中直接获取持仓信息
                position_info = failed_result.etf_config.position if failed_result.etf_config else None
                # 生成错误报告
                report_path = generate_report(failed_result, current_time, model_name, output_dir, position_info)
    
    # 输出处理摘要
    success_count = sum(1 for r in results.values() if r.success)
    fail_count = len(results) - success_count
    
    logger.info("=" * 80)
    logger.info("批量分析完成")
    logger.info(f"总计: {len(results)} 个ETF")
    logger.info(f"成功: {success_count} 个")
    logger.info(f"失败: {fail_count} 个")
    
    if fail_count > 0:
        logger.info("失败的ETF:")
        for code, result in results.items():
            if not result.success:
                logger.info(f"  - {code}: {result.error_message}")
    
    return results


def main() -> None:
    """主函数：批量分析ETF并生成报告."""
    # 配置日志
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    )
    
    # 定义ETF配置
    # ETF代码和名称映射：{"sh515790": "光伏ETF"}
    etf_map = {
        "sh515790": "光伏ETF",
    }
    
    # 创建ETF配置列表
    etf_configs = [
        ETFConfig(code=code, name=name)
        for code, name in etf_map.items()
    ]
    
    # 执行批量分析
    # 并发数设置为3（可以根据需要调整）
    results = batch_analyze_etfs(
        etf_configs=etf_configs,
        max_workers=3,
        output_dir="out",
        model_name="Composer",
    )
    
    # 打印摘要
    print("\n" + "=" * 80)
    print("批量分析完成")
    print("=" * 80)
    for code, result in results.items():
        status = "✓ 成功" if result.success else "✗ 失败"
        print(f"{code} ({result.etf_name}): {status}")
        if not result.success and result.error_message:
            print(f"  错误: {result.error_message}")


if __name__ == "__main__":
    main()

